package com.yuma.tingshu.album.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.hash.BloomFilter;
import com.yuma.tingshu.album.cache.YuMaCache;
import com.yuma.tingshu.album.mapper.AlbumAttributeValueMapper;
import com.yuma.tingshu.album.mapper.AlbumInfoMapper;
import com.yuma.tingshu.album.mapper.AlbumStatMapper;
import com.yuma.tingshu.album.service.AlbumInfoService;
import com.yuma.tingshu.common.constant.RabbitConstant;
import com.yuma.tingshu.common.constant.RedisConstant;
import com.yuma.tingshu.common.constant.SystemConstant;
import com.yuma.tingshu.common.util.AuthContextHolder;
import com.yuma.tingshu.model.album.AlbumAttributeValue;
import com.yuma.tingshu.model.album.AlbumInfo;
import com.yuma.tingshu.model.album.AlbumStat;
import com.yuma.tingshu.query.album.AlbumInfoQuery;
import com.yuma.tingshu.vo.album.AlbumAttributeValueVo;
import com.yuma.tingshu.vo.album.AlbumInfoVo;
import com.yuma.tingshu.vo.album.AlbumListVo;
import com.yuma.tingshu.vo.album.AlbumStatVo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class AlbumInfoServiceImpl extends ServiceImpl<AlbumInfoMapper, AlbumInfo> implements AlbumInfoService {

    @Autowired
    private AlbumInfoMapper albumInfoMapper;
    @Autowired
    private AlbumAttributeValueMapper albumAttributeValueMapper;
    @Autowired
    private AlbumStatMapper albumStatMapper;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Resource(name = "albumBloomFilter")
    private RBloomFilter rBloomFilter;

    @Override
    public List<AlbumStatVo> getAlbumStatsByAlbumIds(List<Long> albumIds) {
        return this.albumStatMapper.getAlbumStatsByAlbumIds(albumIds);
    }

    @Override
    public List<Long> getChangedAlbumIds(String startTime, String endTime) {
        // 更新时间在startTime和endTime之间，查询albumId
        List<AlbumStat> albumStats = this.albumStatMapper.selectList(new LambdaQueryWrapper<AlbumStat>()
                .between(AlbumStat::getUpdateTime, startTime, endTime)
                .select(AlbumStat::getAlbumId));
        // 处理集合
        // 1.判空
        if (!CollectionUtils.isEmpty(albumStats)) {
            //albumStats.stream().map(m -> m.getAlbumId()).distinct().toList();
            // 特殊方法引用    静态方法    类名::静态方法名    非静态方法引用     对象::非静态方法名
            // List 转成Stream，映射(穿一个参数，返回一个值):得到一个新的Stream，AlbumId去重返回新Stream
            return albumStats.stream().map(AlbumStat::getAlbumId).distinct().toList();
        }
        return Collections.EMPTY_LIST;
    }

    @Override
    public AlbumStatVo getAlbumStatByAlbumId(Long albumId) {
        return this.albumStatMapper.getAlbumStatByAlbumId(albumId);
    }

    @Override
    public List<AlbumAttributeValue> findAlbumAttributeValuesByAlbumId(Long albumId) {
        return albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));
    }

    @Override
    public Page<AlbumListVo> findAlbumPage(int page, int limit, AlbumInfoQuery albumInfoQuery) {
        return albumInfoMapper.findUserAlbumPage(new Page<>(page, limit), albumInfoQuery);
    }

    @Override
    public AlbumListVo findAlbumListVo(Long albumId) {
        return albumInfoMapper.findUserAlbumListVo(albumId);
    }

    @Override
    public List<AlbumInfo> findUserAllAlbumList() {
        // mybatisplus自己处理是否删除的
        Long userId = AuthContextHolder.getUserId();
        List<AlbumInfo> list = this.list(new LambdaQueryWrapper<AlbumInfo>()
                .eq(AlbumInfo::getUserId, userId == null ? 1L : userId)
                .select(AlbumInfo::getId, AlbumInfo::getAlbumTitle)
                .orderByDesc(AlbumInfo::getCreateTime));
        return list;
    }

    @Transactional
    @Override
    public void updateAlbumInfo(Long albumId, AlbumInfoVo albumInfoVo) {
        // 更新专辑信息表
        AlbumInfo albumInfo = new AlbumInfo();
        albumInfo.setId(albumId);
        BeanUtils.copyProperties(albumInfoVo, albumInfo);
        this.updateById(albumInfo);

        // 删除专辑标签
        this.albumAttributeValueMapper.delete(new LambdaUpdateWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));
        // 新增专辑标签
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (!CollectionUtils.isEmpty(albumAttributeValueVoList)) {
            // 便利标签值进行保存
            albumAttributeValueVoList.forEach(albumAttributeValueVo -> {
                AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
                BeanUtils.copyProperties(albumAttributeValueVo, albumAttributeValue);
                albumAttributeValue.setAlbumId(albumId);
                this.albumAttributeValueMapper.insert(albumAttributeValue);
            });
        }

        // 私有变公开要新增，公开变私有就remove
        if (albumInfoVo.getIsOpen().equals("1")) {
            // 发消息给MQ，同步数据
            this.rabbitTemplate.convertAndSend(RabbitConstant.ALBUM_EXCHANGE, RabbitConstant.ALBUM_SAVE_RK, albumId);
        } else if (albumInfoVo.getIsOpen().equals("0")) {
            // 删除
            this.rabbitTemplate.convertAndSend(RabbitConstant.ALBUM_EXCHANGE, RabbitConstant.ALBUM_DELETE_RK, albumId);
        }
    }

    @YuMaCache(
            prefix = RedisConstant.ALBUM_INFO_PREFIX,
            bloomFilter = RedisConstant.ALBUM_BLOOM_FILTER,
            lock = RedisConstant.ALBUM_LOCK_PREFIX,
            randomBounds = 1000,
            timeout = RedisConstant.ALBUM_TIMEOUT
    )
    @Override
    public AlbumInfo getAlbumInfo(Long albumId) {
        // 1.查询专辑信息
        AlbumInfo albumInfo = albumInfoMapper.selectById(albumId);
        // 查询标签
        List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));

        albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);

        return albumInfo;
    }
    /// 5.采用缓存的情况下，考虑缓存穿透-->恶意请求访问缓存中不存在的数据，直接请求mysql，mysql中也不存在值
    /*@Override
    public AlbumInfo getAlbumInfo(Long albumId) {
        AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + albumId);
        if (albumInfo != null) {
            return albumInfo;
        }
        // 3.为了防止缓存穿透，使用布隆过滤器, 布隆过滤器中没有的则直接返回
        String key = RedisConstant.ALBUM_INFO_PREFIX + albumId;
        if (!rBloomFilter.contains(key)) {
            return null;
        }

        // 2.解决缓存击穿：加锁
        RLock fairlock = redissonClient.getFairLock(RedisConstant.ALBUM_LOCK_PREFIX + albumId);

        fairlock.lock();
        try {
            // 重新获取缓存数据
            albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + albumId);
            if (albumInfo != null) {
                return albumInfo;
            }

            albumInfo = albumInfoMapper.selectById(albumId);
            List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));
            albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);

            // 1.解决雪崩：过期时间加随机数
            long timeout = RedisConstant.ALBUM_TIMEOUT + ThreadLocalRandom.current().nextInt(1000);

            redisTemplate.opsForValue().set(RedisConstant.ALBUM_INFO_PREFIX + albumId, albumInfo, timeout, TimeUnit.SECONDS);
        } finally {
            // 解锁
            fairlock.unlock();
        }
        return albumInfo;
    }*/
    /**
     * 4.采用缓存的情况下，考虑缓存击穿问题-->某个热点 key 在缓存中过期的瞬间，大量并发请求同时发现缓存为空，全部去查数据库 → 数据库压力暴增甚至被打崩
     *  解决思想：只允许一个线程去查数据库并回填缓存，其他线程等待或重试
     *  互斥锁（如 Redisson RLock）
     */
    /*@Override
	public AlbumInfo getAlbumInfo(Long id) {
		AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
		if (albumInfo != null){
			return albumInfo;
		}
		//2. 解决击穿
		RLock fairLock = redissonClient.getFairLock(RedisConstant.ALBUM_LOCK_PREFIX + id);
		fairLock.lock();
		try {
			// 3.重新获取缓存数据
			albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
			if (albumInfo != null){
				return albumInfo;
			}

			albumInfo = albumInfoMapper.selectById(id);
			List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>()
					.eq(AlbumAttributeValue::getAlbumId, id));
			albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);

			//1. 解决雪崩 : + 随机数
			long timeout = RedisConstant.ALBUM_TIMEOUT + ThreadLocalRandom.current().nextInt(1000);
			redisTemplate.opsForValue().set(RedisConstant.ALBUM_INFO_PREFIX + id, albumInfo, timeout, TimeUnit.SECONDS);
		}finally {
			// 解锁
			fairLock.unlock();
		}
		return albumInfo;
	}*/

    /**
     * 3.采用缓存的情况下，考虑缓存击穿问题-->某个热点 key 在缓存中过期的瞬间，大量并发请求同时发现缓存为空，全部去查数据库 → 数据库压力暴增甚至被打崩
     * 解决思想：只允许一个线程去查数据库并回填缓存，其他线程等待或重试
     * 分布式锁（如 Redis SETNX + 过期时间）
     */
	/*@Override
	public AlbumInfo getAlbumInfo(Long id) {
		AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
		if (albumInfo != null){
			return albumInfo;
		}
		// 2.加锁 Redisson原理
		Boolean result = redisTemplate.opsForValue()
				.setIfAbsent(RedisConstant.ALBUM_LOCK_PREFIX + id, "1",30,TimeUnit.SECONDS);

		if (result) {
			// 时间是不确定的，写一个线程key续期
			var t = new Thread(()->{
				// 每隔10秒让key续期
				while (true) {
					redisTemplate.expire(RedisConstant.ALBUM_LOCK_PREFIX + id, 30, TimeUnit.SECONDS);
					try {
						Thread.sleep(10 * 1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
			albumInfo = albumInfoMapper.selectById(id);
			List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>()
					.eq(AlbumAttributeValue::getAlbumId, id));
			albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);

			long timeout = RedisConstant.ALBUM_TIMEOUT + ThreadLocalRandom.current().nextInt(1000);
			redisTemplate.opsForValue().set(RedisConstant.ALBUM_INFO_PREFIX + id, albumInfo, timeout, TimeUnit.SECONDS);
			// 删除， 特殊情况：redis加了锁，但是服务宕机没有执行到delete这个key
			redisTemplate.delete(RedisConstant.ALBUM_LOCK_PREFIX + id);
			t.interrupt();
		}else{
			albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
			if (albumInfo != null){
				return albumInfo;
			}
		}

		return albumInfo;
	}*/

    /// 2.采用缓存的情况下，考虑缓存雪崩问题-->某一时刻，大量缓存数据同时失效或缓存服务整体宕机，导致所有请求全部打到数据库，造成数据库瞬间压力剧增，甚至被打崩，进而导致整个系统不可用
	/*@Override
	public AlbumInfo getAlbumInfo(Long id) {
		AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
		if (albumInfo != null){
			return albumInfo;
		}

		albumInfo = albumInfoMapper.selectById(id);
		List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>()
				.eq(AlbumAttributeValue::getAlbumId, id));
		albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);

		//1. 解决雪崩 : + 随机数
		long timeout = RedisConstant.ALBUM_TIMEOUT + ThreadLocalRandom.current().nextInt(1000);
		redisTemplate.opsForValue().set(RedisConstant.ALBUM_INFO_PREFIX+id,albumInfo,timeout, TimeUnit.SECONDS);
		return albumInfo;
	}*/

    /// 1.原有功能加上缓存
	/*@Override
	public AlbumInfo getAlbumInfo(Long id) {
		//2.从redis中查询拿数据
		AlbumInfo albumInfo = (AlbumInfo) redisTemplate.opsForValue().get(RedisConstant.ALBUM_INFO_PREFIX + id);
		//3.判别，如果从redis中拿到了对象，直接返回
		if (albumInfo != null){
			return albumInfo;
		}

		// 去数据库中查询
			// 1.查询专辑信息
			albumInfo = albumInfoMapper.selectById(id);
			// 2.根据id查询标签
			List<AlbumAttributeValue> albumAttributeValues = albumAttributeValueMapper.selectList(new LambdaQueryWrapper<AlbumAttributeValue>()
					.eq(AlbumAttributeValue::getAlbumId, id));

			albumInfo.setAlbumAttributeValueVoList(albumAttributeValues);

		// 1.把查询到的albumInfo写入redis中， key -> album:info:1600   value -> albumInfo对象
		redisTemplate.opsForValue().set(RedisConstant.ALBUM_INFO_PREFIX+id,albumInfo,RedisConstant.ALBUM_TIMEOUT, TimeUnit.SECONDS);

		return albumInfo;
	}*/
    @Transactional
    @Override
    public void removeAlbumInfo(Long albumId) {
        // 删除专辑,删除album_info表中指定数据
        //albumInfoMapper.deleteById(albumId);
        //select * from album_info where id = ?
        this.removeById(albumId);
        // 删除标签
        //select * from album_attribute_value where album_id = ?
        albumAttributeValueMapper.delete(new LambdaUpdateWrapper<AlbumAttributeValue>().eq(AlbumAttributeValue::getAlbumId, albumId));
        // 删除统计
        //select * from album_stat where album_id = ?
        albumStatMapper.delete(new LambdaUpdateWrapper<AlbumStat>().eq(AlbumStat::getAlbumId, albumId));

        // 通知MQ删除
        this.rabbitTemplate.convertAndSend(RabbitConstant.ALBUM_EXCHANGE, RabbitConstant.ALBUM_DELETE_RK, albumId);
    }

    @Override
    public Page<AlbumListVo> findUserAlbumPage(Integer index, Integer limit, AlbumInfoQuery query) {
        return albumInfoMapper.findUserAlbumPage(new Page<>(index, limit)/*要想实现分页，必须传入Page对象*/, query);
    }

    @Transactional
    @Override
    public void saveAlbumInfo(AlbumInfoVo albumInfoVo) {
        // 1.保存专辑信息表
        AlbumInfo albumInfo = new AlbumInfo();
        // 把相同属性进行拷贝
        BeanUtils.copyProperties(albumInfoVo, albumInfo);
        // TODO userId先设置默认值，等做了登录以后再改
        Long userId = AuthContextHolder.getUserId();
        albumInfo.setUserId(userId == null ? 1 : userId);
        //	设置专辑审核状态为：通过
        albumInfo.setStatus(SystemConstant.ALBUM_STATUS_PASS);
        //  付费的默认前前5集免费试看
        if (!albumInfoVo.getPayType().equals(SystemConstant.ALBUM_PAY_TYPE_FREE)) {
            albumInfo.setTracksForFree(5);
        }
        //	保存专辑
        this.albumInfoMapper.insert(albumInfo);

        Long albumId = albumInfo.getId();

        // 同步到布隆过滤器
        rBloomFilter.add(RedisConstant.ALBUM_INFO_PREFIX + albumId);

        // 2.保存专辑标签信息
        List<AlbumAttributeValueVo> albumAttributeValueVoList = albumInfoVo.getAlbumAttributeValueVoList();
        if (!CollectionUtils.isEmpty(albumAttributeValueVoList)) {
            // 便利标签值进行保存
            albumAttributeValueVoList.forEach(albumAttributeValueVo -> {
                AlbumAttributeValue albumAttributeValue = new AlbumAttributeValue();
                BeanUtils.copyProperties(albumAttributeValueVo, albumAttributeValue);
                albumAttributeValue.setAlbumId(albumId);
                this.albumAttributeValueMapper.insert(albumAttributeValue);
            });
        }

        // 3.保存初始化统计信息
        initAlbumStat(albumId, SystemConstant.ALBUM_STAT_PLAY);
        initAlbumStat(albumId, SystemConstant.ALBUM_STAT_SUBSCRIBE);
        initAlbumStat(albumId, SystemConstant.ALBUM_STAT_BROWSE);
        initAlbumStat(albumId, SystemConstant.ALBUM_STAT_COMMENT);

        // 非私有的发送消息给MQ
        if (albumInfoVo.getIsOpen().equals("1")) {
            this.rabbitTemplate.convertAndSend(RabbitConstant.ALBUM_EXCHANGE, RabbitConstant.ALBUM_SAVE_RK, albumId);
        }
    }

    /**
     * 初始化统计数据
     *
     * @param albumId
     * @param statType
     */
    private void initAlbumStat(Long albumId, String statType) {
        AlbumStat albumStat = new AlbumStat();
        albumStat.setAlbumId(albumId);
        albumStat.setStatType(statType);
        this.albumStatMapper.insert(albumStat);
    }
}
